package com.bestcem.xm.qdes.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bestcem.xm.bi.grpc.v1.enums.DataTypeEnum;
import com.bestcem.xm.bi.grpc.v1.enums.FuncEnum;
import com.bestcem.xm.bi.grpc.v1.enums.MultiFuncEnum;
import com.bestcem.xm.bi.grpc.v1.enums.OptionEnum;
import com.bestcem.xm.bi.grpc.v1.services.Filter;
import com.bestcem.xm.bi.grpc.v1.services.NewColumn;
import com.bestcem.xm.bi.grpc.v1.services.Pivot;
import com.bestcem.xm.common.core.exception.BusinessException;
import com.bestcem.xm.component.mybatis.base.ResultPage;
import com.bestcem.xm.component.mybatis.service.impl.XmServiceImpl;
import com.bestcem.xm.component.security.context.SecurityContextHolder;
import com.bestcem.xm.component.security.dto.TokenDTO;
import com.bestcem.xm.componet.filestorage.service.StorageService;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.qdes.controller.param.*;
import com.bestcem.xm.qdes.controller.vo.*;
import com.bestcem.xm.qdes.convert.QdesProjectConvertMapper;
import com.bestcem.xm.qdes.entity.*;
import com.bestcem.xm.qdes.enums.TagRefTypeEnum;
import com.bestcem.xm.qdes.grpc.client.BiGrpcClient;
import com.bestcem.xm.qdes.grpc.client.UserGrpcClient;
import com.bestcem.xm.qdes.grpc.client.dto.UserDto;
import com.bestcem.xm.qdes.mapper.*;
import com.bestcem.xm.qdes.service.*;
import com.bestcem.xm.qdes.util.PageUtil;
import com.bestcem.xm.qdes.util.QdesConstant;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.bson.types.ObjectId;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Nonnull;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.bestcem.xm.qdes.grpc.v1.services.ProjectPtype.PROJECT_PTYPE_QUESITON;
import static com.bestcem.xm.qdes.grpc.v1.services.ProjectStatus.PROJECT_STATUS_CREATED;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentDataStatus.RESPONDENT_DATA_STATUS_FORMAL;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentStatus.RESPONDENT_STATUS_FINISH;

/**
 * 描述：模板库-题目库
 * 作者：杨海奇
 * 时间：2022/8/23 9:54
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class QdesQuestionLibServiceImpl extends XmServiceImpl<QdesQuestionLibMapper, QdesQuestionLib> implements QdesQuestionLibService {
    private static final String STRUCT_DEFAULT_DATA_FILE_PATH = "file/struct_default_data.json";
    private static final String PAGE_FILE_PATH = "file/struct_default_data_p_items.json";
    //权限
    private static final int PROJECT_PERMISSION_DELIVER_TASK = 60;

    private static final String STRUCT_FILE_PATH = "file/struct_default_data.json";

    private static final String CUSTOM_ATTR_FILE_PATH = "file/custom_attr.json";

    private @NonNull UserGrpcClient userGrpcClient;

    private @NonNull com.bestcem.xm.qdes.convert.QstructConvertMapper qstructConvertMapper;
    private @NonNull QdesTagRefMapper qdesTagrefMapper;
    private @NonNull QdesTagMapper qdesTagMapper;
    private @NonNull QdesQstructMapper qdesQstructMapper;
    private @NonNull QdesQuestionMapper qdesQuestionMapper;
    private @NonNull QdesProjectMapper qdesProjectMapper;
    private @NonNull QdesProjectSettingMapper qdesProjectsettingMapper;
    private @NonNull QdesProjectFolderMapper qdesProjectfolderMapper;
    private @NonNull QdesProjectEntryMapper qdesProjectentryMapper;
    private @NonNull QdesProjectConvertMapper qdesProjectConvertMapper;

    //private @NonNull OssService ossService;
    private @NonNull StorageService storageService;
    private @NonNull RedisService redisService;

    private @NonNull QdesQuestionService questionService;
    private @NonNull QdesProjectFolderService projectfolderService;
    private @NonNull QdesProjectService projectService;
    private @NonNull QdesQstructService qdesQstructService;

    private @NonNull BiGrpcClient biGrpcClient;

    /**
     * 题目库列表分页查询
     *
     * @param page
     * @param pageParam
     * @return
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    @Override
    public ResultPage<QdesQuestionlibVo> questionlibPageQuery(Page page, QuestionLibPageParam pageParam) {
        if (pageParam.getIsOfficial() == null) {
            pageParam.setIsOfficial(false);
        }

        //自己xml里面写关联查询，返回前端数据是驼峰的，使用@JsonProperty即可
        //resultMap里面有collection，需要特殊处理一下,先查满足条件的分页ids
        page = baseMapper.pageIds(page, pageParam);
        List<String> ids = page.getRecords();
        List<QdesQuestionlibVo> records = ListUtil.list(false);
        if (!CollectionUtils.isEmpty(ids)) {
            records = baseMapper.getPageResultByIds(ids);
            //调用grpc查询创建人
            for (QdesQuestionlibVo obj : records) {
                String creator = obj.getCreator();
                if (StrUtil.isNotEmpty(creator)) {
                    UserDto user = userGrpcClient.getUser(creator);
                    obj.setCreateUserName(user.getName());
                } else {
                    // 若没有创建人 则默认为 系统
                    obj.setCreateUserName("系统");
                }
                //obj.setCreateUserName("系统");
            }
        }
        page.setRecords(records);
        //转换返回结构
        return PageUtil.getResultPage(page.getCurrent(), page.getTotal(), page.getRecords());
    }


    /**
     * 题目库列表查询 不带分页
     *
     * @param pageParam
     * @return
     */
    @Override
    public List<QdesQuestionlibVo> questionlibQuery(QuestionLibPageParam pageParam) {
        if (pageParam.getIsOfficial() == null) {
            pageParam.setIsOfficial(false);
        }

        List<String> ids = baseMapper.getSearchIds(pageParam);
        List<QdesQuestionlibVo> records = ListUtil.list(false);
        if (!CollectionUtils.isEmpty(ids)) {
            records = baseMapper.getPageResultByIds(ids);
        }
        return records;
    }

    /**
     * 题目库-题目模板保存和复制接口
     *
     * @param param
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void questionlibSaveAndCopy(QuestionLibSaveAndCopyParam param) {
        // 创建题库模板
        String sourceProjectId = param.getProjectId();
        boolean withCopy = param.getWithCopy() != null && param.getWithCopy();//是否是拷贝增加模板
        param.setWithCopy(withCopy);
        List<Integer> gidList = param.getGidList();
        String userId = SecurityContextHolder.getUserId();
        String orgId = SecurityContextHolder.getOrgId();

        //查源数据project和struct和question信息
        QdesProject sourceProject = qdesProjectMapper.selectById(sourceProjectId);
        if (sourceProject == null) {
            throw new BusinessException("根据" + sourceProjectId + "未找到项目信息");
        }
        QdesQstruct sourceQstruct = qdesQstructMapper.selectById(sourceProject.getStructId());
        List<QdesQuestion> sourceQuestions;
        if (withCopy) {
            sourceQuestions = qdesQuestionMapper.selectList(new LambdaQueryWrapper<QdesQuestion>()
                    .eq(QdesQuestion::getStructId, sourceQstruct.getId()));
        } else {
            sourceQuestions = qdesQuestionMapper.selectList(new LambdaQueryWrapper<QdesQuestion>()
                    .eq(QdesQuestion::getStructId, sourceQstruct.getId())
                    .in(QdesQuestion::getGid, gidList));
        }

        if (CollUtil.isEmpty(sourceQuestions)) {
            throw new BusinessException("qdes_question is null");
        }

        createQuestionlib(param, userId, orgId, sourceQuestions, sourceProject);
        log.info("End to questionlibSaveAndCopy");
    }

    private void createQuestionlib(QuestionLibSaveAndCopyParam param, String userId, String orgId, List<QdesQuestion> sourceQuestions,
                                   QdesProject sourceProject) {
        boolean withCopy = param.getWithCopy();
        JSONObject tagsDict = param.getTags();
        List<TagLibParam> tagList = param.getTagList();

        //从全局设置里取配置customAttr
        JSONObject customAttr = new JSONObject();
        QdesProjectSetting projectsetting = qdesProjectsettingMapper.selectOne(new
                LambdaQueryWrapper<QdesProjectSetting>().eq(QdesProjectSetting::getOrgId, orgId).last("limit 1"));
        customAttr.put("openGrouping", projectsetting == null ? 0 : projectsetting.getOpenGrouping());
        customAttr.put("openCustomCid", projectsetting == null ? 0 : projectsetting.getOpenCustomCid());
        // 创建新项目、新的结构、新题目关联到新结构
        // 一题一库
        for (QdesQuestion question : sourceQuestions) {
            String title = getTitle(sourceProject.getTitle(), JSON.parseObject(question.getData()));
            // 创建项目
            QdesProject newProject = new QdesProject();
            newProject.setTitle(title);
            newProject.setCreator(userId);
            newProject.setCompanyId(orgId);
            newProject.setPtype(PROJECT_PTYPE_QUESITON.getNumber());
            newProject.setCustomAttr(customAttr.toJSONString());

            newProject.setStatus(PROJECT_STATUS_CREATED.getNumber());
            newProject.setIsCopyDemo(false);
            newProject.setReleaseVer(0);
            QdesProject project = projectService.createOneProject(newProject, true);

            // 如果不是题库复制，需要根据题目gid获取对应标签，否则直接使用传入的标签
            if (!withCopy) {
                tagList = tagsDict.getJSONArray(question.getGid().toString()).toJavaList(TagLibParam.class);
            }
            // 创建tagref

            //创建questionlib表记录
            createTagrefAndQuestionslib(userId, orgId, title, project.getId(), tagList);
            String targetStructId = project.getStructId();
            // 获取新的gid
            int gidIndex = 1; // gids取的下标
            int dispatchCounts = getDispatchCounts(question);
            List<Integer> gids = projectService.acquireProjectGid(project.getId(), dispatchCounts);
            gidIndex = dispatchGid(question, gids, gidIndex);

            // 创建页面
            JSONObject structData = getStructDataSample();
            JSONObject pageJson = getPageSample();
            JSONArray pages = new JSONArray();
            if (customAttr.getInteger("openGrouping") != 0) {
                Integer pgid = gids.get(gidIndex);
                pageJson.put("cid", "P1");
                pageJson.put("gid", pgid);
                pageJson.put("_index", 1);
                JSONObject qJ = new JSONObject();
                qJ.put("_index", 1);
                qJ.put("pGroupGid", 1);
                qJ.put("disp_code", Optional.ofNullable(JSONObject.parseObject(question.getData()).get("disp_code"))
                        .orElse(""));
                qJ.put("gid", JSONObject.parseObject(question.getData()).get("gid"));
                JSONArray itemJs = new JSONArray();
                itemJs.add(qJ);
                pageJson.put("items", itemJs);
                JSONObject js = new JSONObject();
                js.put("data", new JSONArray());
                JSONObject sortJs = new JSONObject();
                sortJs.put("type", "Q");
                sortJs.put("gid", JSONObject.parseObject(question.getData()).get("gid"));
                js.put("sorts", Collections.singletonList(sortJs));
                structData.getJSONObject("groups").put("1", js);
            } else {
                Integer pgid = gids.get(gidIndex);
                pageJson.put("cid", "P1");
                pageJson.put("gid", pgid);
                pageJson.put("_index", 1);
                JSONObject qJ = new JSONObject();
                qJ.put("_index", 1);
                qJ.put("gid", JSONObject.parseObject(question.getData()).get("gid"));
                pageJson.put("items", Collections.singletonList(qJ));
            }
            pages.add(pageJson);
            structData.put("items", pages);
            QdesQstruct qdesQstruct = qdesQstructMapper.selectById(targetStructId);
            JSONObject paramObj = new JSONObject();
            paramObj.put("data", structData);
            qdesQstructService.updateQstruct(qdesQstruct, paramObj, project, false, true);
            // 选相关联的题目添加到题目库的时候要把选项中的origin字段去掉
            JSONObject dataJs = Optional.ofNullable(JSONObject.parseObject(question.getData())).orElse(new JSONObject());
            if (dataJs.get("items") != null) {
                JSONArray array = dataJs.getJSONArray("items");
                for (int i = 0; i < array.size(); i++) {
                    JSONObject item = array.getJSONObject(i);
                    if (item.containsKey("option_origin")) {
                        item.remove("option_origin");
                    }
                }
            }
            // 创建题目
            QdesQuestion newQuestion = new QdesQuestion();
            newQuestion.setStructId(targetStructId);
            newQuestion.setGid(dataJs.getInteger("gid"));
            newQuestion.setData(dataJs.toJSONString());
            newQuestion.setModified(false);
            qdesQuestionMapper.insert(newQuestion);
        }
    }

    private Integer dispatchGid(QdesQuestion question, List<Integer> gids, int gidIndex) {
        // 修改源题目和选项gid为新的gid
        JSONObject qDict = StringUtils.isEmpty(question.getData()) ? new JSONObject()
                : JSONObject.parseObject(question.getData());
        JSONObject gidMap = new JSONObject();

        Integer qgid = gids.get(gidIndex);
        gidIndex++;
        qDict.put("gid", qgid);
        qDict.put("_index", 1);
        qDict.put("seq", qgid); // 题目 补gid

        JSONObject attachData = qDict.getJSONObject("attachData") == null ? new JSONObject() : qDict.getJSONObject("attachData");
        List<JSONObject> items = qDict.getString("items") == null ? new ArrayList<>() : JSONObject
                .parseArray(qDict.getString("items"), JSONObject.class); // 选项
        List<JSONObject> optionGroupList = qDict.getString("option_group_list") == null ? new ArrayList<>() : JSONObject
                .parseArray(qDict.getString("option_group_list"), JSONObject.class); // 选项分组
        List<JSONObject> optionBindList = qDict.getString("option_bind_list") == null ? new ArrayList<>() : JSONObject
                .parseArray(qDict.getString("option_bind_list"), JSONObject.class); // 选项绑定
        List<JSONObject> optionExcludeList = qDict.getString("option_exclude_list") == null ? new ArrayList<>() : JSONObject
                .parseArray(qDict.getString("option_exclude_list"), JSONObject.class); // 排他设置
        List<JSONObject> rowsItems = qDict.getString("rows_items") == null ? new ArrayList<>() : JSONObject
                .parseArray(qDict.getString("rows_items"), JSONObject.class); // 矩阵行
        List<JSONObject> rowGroupList = qDict.getString("row_group_list") == null ? new ArrayList<>() : JSONObject
                .parseArray(qDict.getString("row_group_list"), JSONObject.class); // 矩阵行分组
        List<JSONObject> rowBindList = qDict.getString("row_bind_list") == null ? new ArrayList<>() : JSONObject
                .parseArray(qDict.getString("row_bind_list"), JSONObject.class); // 矩阵行绑定
        List<JSONObject> optionList = qDict.getString("option_list") == null ? new ArrayList<>() : JSONObject
                .parseArray(qDict.getString("option_list"), JSONObject.class); // 级联题选项
        JSONObject allowNull = qDict.getJSONObject("allowNull") == null ? new JSONObject() : qDict.getJSONObject("allowNull"); // 矩阵题单元格不可填写

        // 选项 补gid  复制oid
        for (JSONObject itemDict : items) {
            Integer gid = gids.get(gidIndex);
            gidIndex++;
            String oid = itemDict.getString("oid");
            gidMap.put(itemDict.getString("gid"), gid);
            itemDict.put("gid", gid);
            itemDict.put("seq", gid);
            itemDict.put("oid", oid);
        }

        // attachData 更新 禁止选项
        if (!attachData.isEmpty() && attachData.containsKey("excludeOptionList")) {
            JSONArray newExlOptionList = new JSONArray();
            for (String exlOp : attachData.getJSONArray("excludeOptionList").toJavaList(String.class)) {
                newExlOptionList.add(gidMap.get(exlOp));
            }
            attachData.put("excludeOptionList", newExlOptionList);
            qDict.put("attachData", attachData);
        }

        JSONObject ogidDict = new JSONObject();
        for (JSONObject item : items) {
            ogidDict.put(item.getString("oid"), item.get("gid"));
        }
        JSONObject ogroupDict = new JSONObject(); // 选项oid: 分组id 字典
        // 选项组 补id
        for (int oIndex = 0; oIndex < optionGroupList.size(); oIndex++) {
            int ogIndex = oIndex + 1;
            JSONObject itemDict = optionGroupList.get(oIndex);
            String ogroupId = qgid + "_" + ogIndex;
            List<JSONObject> itemList = JSONObject.parseArray(itemDict.getString("items"), JSONObject.class);
            itemList = itemList == null ? new ArrayList<>() : itemList;
            for (JSONObject oitemDict : itemList) {
                Object tmpOgid = ogidDict.get(oitemDict.getString("oid"));
                oitemDict.put("gid", tmpOgid);
                ogroupDict.put(oitemDict.getString("oid"), ogroupId);
            }
            // 其他分组 id 命名:qgid_OTHER_GROUP
            if (optionGroupList.size() == ogIndex && "其他分组".equals(itemDict.getString("title"))) {
                ogroupId = qgid + "_OTHER_GROUP";
            }
            itemDict.put("items", itemList);
            itemDict.put("id", ogroupId);
        }
        if (!optionGroupList.isEmpty()) {
            qDict.put("option_group_list", optionGroupList);
        }

        // 选项绑定 补id
        for (int oIndex = 0; oIndex < optionBindList.size(); oIndex++) {
            int obIndex = oIndex + 1;
            JSONObject bindDict = optionBindList.get(oIndex);
            String obindId = qgid + "_" + obIndex;
            List<JSONObject> obItems = JSONObject.parseArray(bindDict.getString("items"), JSONObject.class);
            obItems = obItems == null ? new ArrayList<>() : obItems;
            for (JSONObject tmpItemDict : obItems) {
                Object tmpOgid = ogidDict.get(tmpItemDict.getString("oid"));
                tmpItemDict.put("gid", tmpOgid);
                String pGroupId = ogroupDict.getString(tmpItemDict.getString("oid"));
                if (StringUtils.isNotEmpty(pGroupId)) {
                    tmpItemDict.put("pGroupId", pGroupId);
                }
            }
            bindDict.put("items", obItems);
            bindDict.put("id", obindId);
        }
        if (!optionBindList.isEmpty()) {
            qDict.put("option_bind_list", optionBindList);
        }

        // 选项排他 补id
        for (int oeIndex = 0; oeIndex < optionExcludeList.size(); oeIndex++) {
            JSONObject optionExcludeDict = optionExcludeList.get(oeIndex);
            String oexcludeId = qgid + "_" + (oeIndex + 1);
            List<JSONObject> oeItems = JSONObject.parseArray(optionExcludeDict.getString("items"), JSONObject.class);
            oeItems = oeItems == null ? new ArrayList<>() : oeItems;
            for (JSONObject temItemDict : oeItems) {
                Object tmpOid = ogidDict.get(temItemDict.getString("oid"));
                temItemDict.put("gid", tmpOid);
            }
            optionExcludeDict.put("items", oeItems);
            optionExcludeDict.put("id", oexcludeId);
        }
        if (!optionExcludeList.isEmpty()) {
            qDict.put("option_exclude_list", optionExcludeList);
        }

        // 矩阵行 补id 复制oid
        for (JSONObject itemDict : rowsItems) {
            Integer ogid = gids.get(gidIndex);
            gidIndex++;
            Object oid = itemDict.get("oid");
            gidMap.put(itemDict.getString("gid"), ogid);
            itemDict.put("gid", ogid);
            itemDict.put("seq", ogid);
            itemDict.put("oid", oid);
        }
        // 矩阵行oid: ogid 字典
        JSONObject rgidDict = new JSONObject();
        rowsItems.forEach(rowsItem -> {
            rgidDict.put(rowsItem.getString("oid"), rowsItem.get("gid"));
        });
        if (!rowsItems.isEmpty()) {
            qDict.put("rows_items", rowsItems);
        }

        JSONObject rgroupDict = new JSONObject();
        // 矩阵行分组 补id
        for (int i = 0; i < rowGroupList.size(); i++) {
            int rgIndex = i + 1;
            JSONObject rgItemDict = rowGroupList.get(i);
            String rgroupId = qgid + "_" + rgIndex;
            List<JSONObject> ritems = JSONObject.parseArray(rgItemDict.getString("items"), JSONObject.class);
            ritems = ritems == null ? new ArrayList<>() : ritems;
            for (JSONObject ritemDict : ritems) {
                Object tmpRgid = rgidDict.get(ritemDict.getString("oid"));
                ritemDict.put("gid", tmpRgid);
                rgroupDict.put(ritemDict.getString("oid"), rgroupId);
            }
            if (rowGroupList.size() == rgIndex && "其他分组".equals(rgItemDict.getString("title"))) {
                rgroupId = qgid + "_OTHER_GROUP";
            }
            rgItemDict.put("items", ritems);
            rgItemDict.put("id", rgroupId);
        }
        if (!rowGroupList.isEmpty()) {
            qDict.put("row_group_list", rowGroupList);
        }

        // 矩阵行绑定 补id
        for (int rbIndex = 0; rbIndex < rowBindList.size(); rbIndex++) {
            JSONObject rowBindDict = rowBindList.get(rbIndex);
            String rbindId = qgid + "_" + (rbIndex + 1);
            List<JSONObject> rbItems = JSONObject.parseArray(rowBindDict.getString("items"), JSONObject.class);
            rbItems = rbItems == null ? new ArrayList<>() : rbItems;
            for (JSONObject tmpItemDict : rbItems) {
                String tmpRoid = tmpItemDict.getString("oid");
                Object tmpRgid = rgidDict.get(tmpRoid);
                String pGroupId = rgroupDict.getString(tmpRoid);
                tmpItemDict.put("gid", tmpRgid);
                if (StringUtils.isNotEmpty(pGroupId)) {
                    tmpItemDict.put("pGroupId", pGroupId);
                }
            }
            rowBindDict.put("items", rbItems);
            rowBindDict.put("id", rbindId);
        }
        if (!rowBindList.isEmpty()) {
            qDict.put("row_bind_list", rowBindList);
        }

        // 级联题 补gid
        for (JSONObject optionDict : optionList) {
            Integer ogid = gids.get(gidIndex);
            gidIndex++;
            optionDict.put("gid", ogid);
            optionDict.put("seq", ogid);
        }
        if (!optionList.isEmpty()) {
            qDict.put("option_list", optionList);
        }

        // 矩阵题单元格不可填写
        if (!allowNull.isEmpty()) {
            JSONObject newAllowNull = new JSONObject();
            for (String ky : allowNull.keySet()) {
                Object value = allowNull.get(ky);
                String[] tmpGidList = ky.split("__");
                String rgid = gidMap.getString(tmpGidList[0]);
                String ogid = gidMap.getString(tmpGidList[0]);
                newAllowNull.put(rgid + "__" + ogid, value);
            }
            qDict.put("allowNull", newAllowNull);
        }
        question.setData(qDict.toJSONString());
        return gidIndex;
    }

    private JSONObject getStructDataSample() {
        String attr = ResourceUtil.readUtf8Str(STRUCT_DEFAULT_DATA_FILE_PATH);
        //String urlPrefix = StrUtil.removeSuffix(ossService.getCdn() + ossService.getFilePrefix(), "/");
        String urlPrefix = StrUtil.removeSuffix(storageService.getPropertiesValueBy(null, "domain") + storageService.getPropertiesValueBy(null, "base-path"), "/");
        return JSON.parseObject(String.format(attr, urlPrefix, urlPrefix, urlPrefix));
    }

    private JSONObject getPageSample() {
        String attr = ResourceUtil.readUtf8Str(PAGE_FILE_PATH);
        return JSON.parseObject(attr);
    }

    // 获取需要分配的数量
    private int getDispatchCounts(QdesQuestion question) {
        int dispatchCounts = 0;
        dispatchCounts++;
        JSONObject qDict = StringUtils.isEmpty(question.getData()) ? new JSONObject() : JSONObject
                .parseObject(question.getData());
        JSONArray items = qDict.getJSONArray("items") == null ? new JSONArray() : qDict.getJSONArray("items"); // 选项
        JSONArray rowsItems = qDict.getJSONArray("rows_items") == null ? new JSONArray() : qDict.getJSONArray("rows_items"); // 矩阵行
        JSONArray optionList = qDict.getJSONArray("option_list") == null ? new JSONArray() : qDict.getJSONArray("option_list");
        dispatchCounts += items.size() + rowsItems.size() + optionList.size();
        return dispatchCounts + 10;
    }

    /**
     * 获取title
     *
     * @param projectTitle
     * @param data
     * @return
     */
    private String getTitle(String projectTitle, JSONObject data) {
        String title = data.getString("title");
        title = StrUtil.isEmpty(title) ? projectTitle : title;
        title = RegExUtils.replaceAll(title, "<[^>]+>", "");
        title = title.replaceAll("&nbsp;", "");
        return title;
    }

    /**
     * @param userId
     * @param orgId
     * @param title
     * @param projectId
     * @return
     */
    @NotNull
    private QdesQuestionLib createTagrefAndQuestionslib(String userId, String orgId, String title, String projectId,
                                                        List<TagLibParam> tagList) {
        // 创建questionslib
        QdesQuestionLib questionlib = new QdesQuestionLib();
        questionlib.setTitle(title);
        questionlib.setProjectId(projectId);
        questionlib.setCompanyId(orgId);
        questionlib.setCreator(userId);
        questionlib.setCreatedId(userId);
        //默认值
        questionlib.setIsOfficial(false);//默认false
        questionlib.setIsCopyDemo(false);//默认false
        baseMapper.insert(questionlib);
        log.info("创建questionlib表记录结果：{}", questionlib);
        // 创建tagref
        if (!CollectionUtils.isEmpty(tagList)) {
            saveQdesTagref(tagList, userId, orgId, projectId, questionlib.getId());
        }
        return questionlib;
    }

    /**
     * saveQdesTagref
     *
     * @param tags
     * @param userId
     * @param orgId
     * @param projectId
     * @param refId
     */
    private void saveQdesTagref(List<TagLibParam> tags, String userId, String orgId, String projectId, String refId) {
        if (CollUtil.isEmpty(tags)) {
            return;
        }
        List<QdesTagRef> qdesTagRefs = new ArrayList<>();
        List<QdesTag> filterTags = qdesTagMapper.selectBatchIds(tags.stream().map(TagLibParam::getTagId).collect(Collectors.toList()));
        for (QdesTag tag : filterTags) {
            QdesTagRef qdesTagref = new QdesTagRef();
            qdesTagref.setTagId(tag.getId());
            qdesTagref.setTagTitle(tag.getTitle());
            qdesTagref.setRefType(TagRefTypeEnum.LIBRARY.getValue());
            qdesTagref.setProjectId(projectId);
            qdesTagref.setRefId(refId);
            qdesTagref.setCompanyId(orgId);
            qdesTagref.setCreator(userId);
            qdesTagref.setCreatedId(userId);
            qdesTagref.setIsCopyDemo(false);
            qdesTagRefs.add(qdesTagref);
        }
        int i = qdesTagrefMapper.insertBatchSomeColumn(qdesTagRefs);
        log.info("创建QdesTagref表记录结果：{}", i);
    }

    /**
     * 获取题目的data
     * python那边这里去掉了几个规则字段：
     * 'codeSelect': '自定义代码显示开关',
     * 'defaultOptionSelect': '默认选择逻辑',
     * 'disp_code': '自定义代码',
     * 'jump_code': '跳转逻辑',
     * 'answer_remind': '答题提醒',
     * 'dispSelect': '显示逻辑'
     *
     * @param data
     * @return
     */
    private String getQuetionData(JSONObject data) {
        data.remove("codeSelect");
        data.remove("defaultOptionSelect");
        data.remove("disp_code");
        data.remove("jump_code");
        data.remove("answer_remind");
        data.remove("dispSelect");
        return data.toJSONString();
    }

    /**
     * 题目库-题目模板设置标签
     *
     * @param id    这个id是questionlib的主键，特么下面删除用的是projectId，不排除questionlib.id=project.id
     * @param param
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void questionlibSetTag(String id, QuestionLibSetTagParam param) {
        LambdaQueryWrapper<QdesQuestionLib> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(QdesQuestionLib::getId, QdesQuestionLib::getProjectId);//建议每个查询都加列，只查指定列减少io
        wrapper.eq(QdesQuestionLib::getId, id).last("limit 1");
        QdesQuestionLib qdesQuestionlib = baseMapper.selectOne(wrapper);
        if (qdesQuestionlib == null || StrUtil.isEmpty(qdesQuestionlib.getId())) {
            throw new BusinessException("根据id未找到题目模板");
        }

        //逻辑是先删除旧标签，然后插入新标签
        //删除旧tag
        //boolean del1 = qdesTagrefService.remove(new LambdaQueryWrapper<QdesTagref>().eq(QdesTagref::getRefId, id));
        int del1 = qdesTagrefMapper.delete(new LambdaQueryWrapper<QdesTagRef>().eq(QdesTagRef::getRefId, id));
        log.info("删除ds_qdes_tagref 结果：{}", del1);

        //插入新tag
        List<TagLibParam> tagList = param.getTagList();
        if (CollUtil.isEmpty(tagList)) {
            log.info("tagList is null");
            return;
        }
        ArrayList<QdesTagRef> qdesTagRefs = new ArrayList<>();
        //LocalDateTime now = LocalDateTime.now();
        String userId = SecurityContextHolder.getUserId();
        String orgId = SecurityContextHolder.getOrgId();
        int refType = TagRefTypeEnum.LIBRARY.getValue();
        String projectId = qdesQuestionlib.getProjectId();
        for (TagLibParam tagLibParam : tagList) {
            QdesTagRef tagref = new QdesTagRef();
            tagref.setId(new ObjectId().toHexString());
            tagref.setRefId(id);
            tagref.setTagId(tagLibParam.getTagId());
            tagref.setTagTitle(tagLibParam.getTagTitle());
            tagref.setCreator(userId);
            /*请看@com.bestcem.xm.component.mybatis.handler.MybatisPlusMetaObjectHandler*/
            //tagref.setCreatedDt(now);
            tagref.setCreatedId(userId);
            //tagref.setUpdatedDt(now);
            tagref.setUpdatedId(userId);
            tagref.setCompanyId(orgId);
            tagref.setRefType(refType);
            tagref.setProjectId(projectId);
            qdesTagRefs.add(tagref);
        }
        //boolean saveBatch = qdesTagrefService.saveBatch(qdesTagrefs);
        int saveBatch = qdesTagrefMapper.insertBatchSomeColumn(qdesTagRefs);
        log.info("标签数量:{},保存结果：{}", qdesTagRefs.size(), saveBatch);
    }

    /**
     * 题目库-题目模板删除
     *
     * @param projectId 项目id
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public DeletedCountVo questionlibDelete(String projectId) {

        //校验题目是否存在：这里直接抄的python逻辑，但是没啥必要校验这步，直接删除看删除结果即可
        QdesQuestionLib qdesQuestionlib = baseMapper.selectOne(new LambdaQueryWrapper<QdesQuestionLib>().select(QdesQuestionLib::getId).eq(QdesQuestionLib::getProjectId, projectId).last("limit 1"));
        if (qdesQuestionlib == null || StrUtil.isEmpty(qdesQuestionlib.getId())) {
            throw new BusinessException("根据project id未找到题目模板");
        }

        //删除相关表记录
        //删除ds_qdes_questionlib
        //boolean del1 = remove(new LambdaQueryWrapper<QdesQuestionlib>().eq(QdesQuestionlib::getProjectId, projectId));
        int del1 = baseMapper.delete(new LambdaQueryWrapper<QdesQuestionLib>().eq(QdesQuestionLib::getProjectId, projectId));
        log.info("删除ds_qdes_questionlib 结果：{}", del1);

        //删除ds_qdes_tagref
        //boolean del2 = qdesTagrefService.remove(new LambdaQueryWrapper<QdesTagref>().eq(QdesTagref::getProjectId, projectId));
        int del2 = qdesTagrefMapper.delete(new LambdaQueryWrapper<QdesTagRef>().eq(QdesTagRef::getProjectId, projectId));
        log.info("删除ds_qdes_tagref 结果：{}", del2);

        //查 ds_qdes_qstruct 获取id，然后先删掉 ds_qdes_question ，再删 ds_qdes_qstruct
        //List<String> qstructIds = qdesQstructService.list(queryWrapper).stream().map(QdesQstruct::getId).collect(Collectors.toList());
        List<String> qstructIds = qdesQstructMapper.selectList(new LambdaQueryWrapper<QdesQstruct>().select(QdesQstruct::getId).eq(QdesQstruct::getProjectId, projectId)).stream().map(QdesQstruct::getId).collect(Collectors.toList());
        log.info("qstructIds.size={}", qstructIds.size());
        if (CollUtil.isNotEmpty(qstructIds)) {
            //删除ds_qdes_question
            //boolean del3 = qdesQuestionService.remove(new LambdaQueryWrapper<QdesQuestion>().in(QdesQuestion::getStructId, qstructIds));
            int del3 = qdesQuestionMapper.delete(new LambdaQueryWrapper<QdesQuestion>().in(QdesQuestion::getStructId, qstructIds));
            log.info("删除ds_qdes_question 结果：{}", del3);

            //删除ds_qdes_qstruct
            //boolean del4 = qdesQstructService.removeBatchByIds(qstructIds);
            int del4 = qdesQstructMapper.deleteBatchIds(qstructIds);
            log.info("删除ds_qdes_qstruct 结果：{}", del4);
        }

        //删除ds_qdes_project
        //int del5 = qdesProjectService.deleteById(projectId);
        int del5 = qdesProjectMapper.deleteById(projectId);
        log.info("删除ds_qdes_project 结果：{}", del5);

        //响应结果
        DeletedCountVo deletedCountVo = new DeletedCountVo();
        deletedCountVo.setDeletedCount(del5);
        return deletedCountVo;
    }

    /**
     * 查询问卷项目简要信息
     * title=
     * search_type=all
     * new_sort=-top_order,-updated_dt
     *
     * @param param
     * @return 只有简单信息
     */
    @Override
    public List<QuestionLibProjectVo> projectsSumary(QuestionLibProjectParam param) {
        //查询
        log.info("请求参数：{}", JSON.toJSONString(param));
        //await self.check_current_is_checker(jwt_payload)
        boolean checkPermission = userGrpcClient.checkCurrentChecker(SecurityContextHolder.getUserId(), SecurityContextHolder.getOrgId());
        //query_params = await self.gen_filter()
        JSONObject query_params = genfilterParams(param, checkPermission);
        LambdaQueryWrapper<QdesProjectEntry> wrapper = buildSimpelWrapper(query_params);

        wrapper.select(QdesProjectEntry::getId, QdesProjectEntry::getProjectId, QdesProjectEntry::getTitle);
        wrapper.orderByDesc(QdesProjectEntry::getUpdatedDt);

        List<QuestionLibProjectVo> projectVoList = qdesProjectentryMapper.selectList(wrapper).stream().map(qdesProjectentry -> {
            QuestionLibProjectVo vo = new QuestionLibProjectVo();
            vo.setId(qdesProjectentry.getProjectId());
            vo.setTitle(qdesProjectentry.getTitle());
            return vo;
        }).collect(Collectors.toList());

        if (CollUtil.isEmpty(projectVoList)) {
            return new ArrayList<>();
        }
        log.info("查的结果size：{}", projectVoList.size());
        return projectVoList;
    }

    /*
     * 构建查all的条件
     * @author chenglong.yue <chenglong.yue@idiaoyan.com>
     * @date 2022/11/1 17:14
     * @param query_params
     * @return com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.bestcem.xm.qdes.entity.QdesProjectentry>
     */
    private LambdaQueryWrapper<QdesProjectEntry> buildSimpelWrapper(JSONObject query_params) {
        LambdaQueryWrapper<QdesProjectEntry> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(QdesProjectEntry::getOrgId, SecurityContextHolder.getOrgId())
                .eq(StrUtil.isNotEmpty(query_params.getString("status")), QdesProjectEntry::getStatus, query_params.getString("status"))
                .like(StrUtil.isNotEmpty(query_params.getString("title")), QdesProjectEntry::getTitle, query_params.getString("title"))
                .eq(StrUtil.isNotEmpty(query_params.getString("user_id")), QdesProjectEntry::getUserId, query_params.getString("user_id"));

        //String folder_id = query_params.getString("folder_id");
        //if (StrUtil.isBlank(folder_id)) {
        //    return wrapper;
        //}
        //
        ////do_all走不到这里来
        //if (folder_id.contains(",")) {
        //    //转成notin查询
        //    //params['folder_id'] = {'$nin': folder_id_list}
        //    wrapper.notIn(QdesProjectentry::getFolderId, folder_id.split(","));
        //} else {
        //    wrapper.eq(QdesProjectentry::getFolderId, folder_id);
        //}
        return wrapper;
    }

    private Boolean needQueryList(String str) {
        if (StrUtil.isNotBlank(str) && str.contains(",")) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    private JSONObject genfilterParams(QuestionLibProjectParam param, boolean checkPermission) {
        JSONObject query_params = (JSONObject) JSONObject.toJSON(param);
        String category = Optional.ofNullable(query_params.getOrDefault("category", "0").toString()).orElse("0");

        //管理员可以看所有项目，所以随便一个管理员user_id就能查询所有数据
        TokenDTO token = SecurityContextHolder.getToken();
        if (token.isAdmin() && !"1".equals(category)) {
            if (NumberUtils.INTEGER_ONE.equals(token.getSuperRole())) {
                List<String> adminIds = userGrpcClient.listOrgAdminIds(SecurityContextHolder.getOrgId());
                if (!CollectionUtils.isEmpty(adminIds)) {
                    query_params.put("user_id", adminIds.stream().findFirst().orElse(""));
                }
            } else {
                query_params.put("user_id", SecurityContextHolder.getUserId());
            }
        } else if (!checkPermission || "1".equals(category)) {
            query_params.put("user_id", SecurityContextHolder.getUserId());
        } else {
            //如果是审核者，则通过公司管理员查询出所有项目
            List<String> adminIds = userGrpcClient.listOrgAdminIds(SecurityContextHolder.getOrgId());
            if (!CollectionUtils.isEmpty(adminIds)) {
                query_params.put("user_id", adminIds.stream().findFirst().orElse(""));
            }
        }
        return query_params;
    }

    /**
     * 处理排序
     *
     * @param qw
     * @param newSort 格式：-a,b,-c。-代表desc，反之asc，逗号分割多个条件
     */
    private void dealOrderby(QueryWrapper<QdesProjectEntry> qw, String newSort) {
        String[] split = newSort.split(",");
        if (ArrayUtil.isNotEmpty(split)) {
            for (String s : split) {
                if (StrUtil.isNotEmpty(s)) {
                    boolean isDesc = s.startsWith("-");
                    qw.orderBy(true, !isDesc, isDesc ? s.substring(1) : s);
                }
            }
        }
    }

    /**
     * 分页查询项目信息
     *
     * @param page
     * @param param
     * @return
     */
    @Override
    public QuestionLibProjectHomeVo projects(Page<QdesProjectEntry> page, QuestionLibProjectParam param) {
        log.info("请求参数：{}", JSON.toJSONString(param));
        JSONObject query_params = (JSONObject) JSONObject.toJSON(param);
        query_params.remove("userId");
        query_params.remove("orgId");
        //await self.check_current_is_checker(jwt_payload)
        boolean checkPermission = userGrpcClient.checkCurrentChecker(SecurityContextHolder.getUserId(), SecurityContextHolder.getOrgId());
        //query_params = await self.gen_filter()
        JSONObject kwargs = genfilterParams(param, checkPermission);
        if (!kwargs.isEmpty()) {
            query_params.putAll(kwargs);
        }

        String title = query_params.getString("title");
        String user_id = query_params.getString("user_id");
        String org_id = SecurityContextHolder.getOrgId();
        String top_order = query_params.getString("top_order");
        String new_sort = query_params.getString("new_sort");

        String project_id = query_params.getString("project_id");
        String category = query_params.getString("category");
        String perm_type = query_params.getString("perm_type");
        String folder_id = query_params.getString("folder_id");
        String status = query_params.getString("status");
        String badge_status = query_params.getString("badge_status");
        String permissions = query_params.getString("permissions");

        QueryWrapper<QdesProjectEntry> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .like(StrUtil.isNotEmpty(title), QdesProjectEntry::getTitle, title)
                .eq(StrUtil.isNotEmpty(org_id), QdesProjectEntry::getOrgId, org_id)
                .eq(StrUtil.isNotEmpty(user_id), QdesProjectEntry::getUserId, user_id)

                .eq(StrUtil.isNotEmpty(project_id) && !needQueryList(project_id), QdesProjectEntry::getProjectId, Optional.ofNullable(project_id).orElse(""))
                .in(StrUtil.isNotEmpty(project_id) && needQueryList(project_id), QdesProjectEntry::getProjectId, (Object) Optional.ofNullable(project_id).orElse("").split(","))

                .eq(StrUtil.isNotEmpty(category) && !needQueryList(category), QdesProjectEntry::getCategory, Optional.ofNullable(category).orElse(""))
                .in(StrUtil.isNotEmpty(category) && needQueryList(category), QdesProjectEntry::getCategory, (Object) Optional.ofNullable(category).orElse("").split(","))

                .eq(StrUtil.isNotEmpty(perm_type) && !needQueryList(perm_type), QdesProjectEntry::getPermType, Optional.ofNullable(perm_type).orElse(""))
                .in(StrUtil.isNotEmpty(perm_type) && needQueryList(perm_type), QdesProjectEntry::getPermType, (Object) Optional.ofNullable(perm_type).orElse("").split(","))

                .eq(StrUtil.isNotEmpty(folder_id) && !needQueryList(folder_id), QdesProjectEntry::getFolderId, Optional.ofNullable(folder_id).orElse(""))
                .in(StrUtil.isNotEmpty(folder_id) && needQueryList(folder_id), QdesProjectEntry::getFolderId, (Object) Optional.ofNullable(folder_id).orElse("").split(","))

                .eq(StrUtil.isNotEmpty(status) && !needQueryList(status), QdesProjectEntry::getStatus, Optional.ofNullable(status).orElse(""))
                .in(StrUtil.isNotEmpty(status) && needQueryList(status), QdesProjectEntry::getStatus, (Object) Optional.ofNullable(status).orElse("").split(","))

                .eq(StrUtil.isNotEmpty(badge_status) && !needQueryList(badge_status), QdesProjectEntry::getBadgeStatus, Optional.ofNullable(badge_status).orElse(""))
                .in(StrUtil.isNotEmpty(badge_status) && needQueryList(badge_status), QdesProjectEntry::getBadgeStatus, (Object) Optional.ofNullable(badge_status).orElse("").split(","))

                .eq(StrUtil.isNotEmpty(top_order) && !needQueryList(top_order), QdesProjectEntry::getTopOrder, Optional.ofNullable(top_order).orElse(""))
                .in(StrUtil.isNotEmpty(top_order) && needQueryList(top_order), QdesProjectEntry::getTopOrder, (Object) Optional.ofNullable(top_order).orElse("").split(","))

                .apply(StrUtil.isNotEmpty(permissions), "JSON_CONTAINS(permissions_arry, JSON_ARRAY(" + Optional.ofNullable(permissions).orElse("") + "))");

        // 未分组的查询
        if (StrUtil.EMPTY.equals(folder_id)) {
            LambdaQueryWrapper<QdesProjectFolder> entryWrapper = Wrappers.lambdaQuery();
            entryWrapper.eq(QdesProjectFolder::getOrgId, SecurityContextHolder.getOrgId())
                    .eq(QdesProjectFolder::getUserId, SecurityContextHolder.getUserId())
                    .last("limit 1");
            QdesProjectFolder folder = qdesProjectfolderMapper.selectOne(entryWrapper);
            if (!Objects.isNull(folder)) {
                List<String> ids = new ArrayList<>();
                projectfolderService.foldersTree(JSONArray.parseArray(folder.getFolders()), ids);
                queryWrapper.lambda().notIn(ids.size() > 0, QdesProjectEntry::getFolderId, ids);
            }
        }
        //new_sort=-top_order,-updated_dt。-代表desc，默认asc
        if (StrUtil.isNotEmpty(new_sort)) {
            dealOrderby(queryWrapper, new_sort);
        }
        Page<QdesProjectEntry> qdesProjectentryPage = qdesProjectentryMapper.selectPage(page, queryWrapper);

        long total = qdesProjectentryPage.getTotal();
        List<QdesProjectEntry> records = qdesProjectentryPage.getRecords();
        if (CollUtil.isEmpty(records)) {
            return new QuestionLibProjectHomeVo(1L, 0L, new ArrayList<>());
        }

        //转换，顺便把projectId取出来后面用
        Set<String> userPageProjectIds = new HashSet<>();
        List<QuestionLibProjectHomePageVo> homePageVos = records.stream().map(projectEntry -> {
            QuestionLibProjectHomePageVo homePageVo = new QuestionLibProjectHomePageVo();
            BeanUtil.copyProperties(projectEntry, homePageVo);
            //取projectId
            userPageProjectIds.add(projectEntry.getProjectId());
            return homePageVo;
        }).collect(Collectors.toList());
        log.info("查的结果size：{}", homePageVos.size());

        //补充数据取数
        //项目信息
        List<QdesProject> projects = qdesProjectMapper.selectBatchIds(userPageProjectIds);//项目
        Map<String, QdesProject> projectMap = projects.stream().collect(Collectors.toMap(QdesProject::getId, Function.identity()));
        //用户信息
        Set<String> creators = projects.stream().map(QdesProject::getCreator).collect(Collectors.toSet());
        List<UserDto> userList = userGrpcClient.listUserByUserIds(ListUtil.toList(creators));
        Map<String, UserDto> userMap = userList.stream().collect(Collectors.toMap(UserDto::getId, Function.identity()));
        //结构信息
        Set<String> structIds = projects.stream().map(QdesProject::getStructId).collect(Collectors.toSet());
        List<QdesQstruct> qdesQstructs = qdesQstructMapper.selectBatchIds(structIds);
        Map<String, Integer> qCountMap = qdesQstructs.stream().collect(Collectors.toMap(QdesQstruct::getId, this::getQcount));
        String userId = SecurityContextHolder.getUserId();
        String orgId = SecurityContextHolder.getOrgId();
        //补充数据
        homePageVos.forEach(vo -> {
            QdesProject project = projectMap.get(vo.getProjectId());//不为空
            UserDto userV1 = userMap.get(project.getCreator());//可能为空

            vo.setIsCopyDemo(project.getIsCopyDemo());
            vo.setCopyFinished(getCopyFinished(project, orgId));
            fillNpsInfo(vo, project);

            //vo.setFinishedCount((Integer) projectService.getFinishedRspdCount(project.getId(), RESPONDENT_DATA_STATUS_FORMAL.getNumber(), RESPONDENT_STATUS_FINISH.getNumber()).getOrDefault("count", 0));//答卷数
            //vo.setSurveyFinishedList((List<Integer>) projectService.getFinishedRspdCount(project.getId(), RESPONDENT_DATA_STATUS_FORMAL.getNumber(), RESPONDENT_STATUS_FINISH.getNumber()).getOrDefault("survey_finished_list", ListUtil.of(0, 0, 0)));//默认值

            //Collection<Integer> surveyFinishedList = projectService.getFinishedRspdCountV2(project.getId(), RESPONDENT_DATA_STATUS_FORMAL.getNumber(), RESPONDENT_STATUS_FINISH.getNumber()).get("survey_finished_list");
            //List<Integer> nums = new ArrayList<>(surveyFinishedList);
            //vo.setSurveyFinishedList(nums);

            //survey_finished_list暂时设置为0 ，兼容一下前端老代码，具体数值由另一个接口提供/respondent/num/
            vo.setSurveyFinishedList(ListUtil.of(0, 0, 0));
            Collection<Integer> countList = projectService.getFinishedRspdCountV2(project.getId(), RESPONDENT_DATA_STATUS_FORMAL.getNumber(), RESPONDENT_STATUS_FINISH.getNumber()).get("count");
            Integer count = countList.iterator().next();
            vo.setFinishedCount(count == null ? 0 : count);//答卷数;

            vo.setQCount(qCountMap.get(project.getStructId()));
            vo.setCreator(getCreator(userV1));
            vo.setCreatedDt(project.getCreatedDt());
            vo.setPermissionsArry(addPermission(vo, userId, checkPermission));//添加权限
        });

        //20221102  is_copy_demo=true 查不到数据，后续代码也没必要写了
        //查询非当前用户的模板项目id
        //LambdaQueryWrapper<QdesProject> sampleProjectWrapper = new LambdaQueryWrapper<>();
        //sampleProjectWrapper.select(QdesProject::getId);
        //sampleProjectWrapper.eq(QdesProject::getCompanyId, orgId);
        //sampleProjectWrapper.ne(QdesProject::getCreator, userId);
        //sampleProjectWrapper.eq(QdesProject::getIsCopyDemo, true);
        //sampleProjectWrapper.in(QdesProject::getStatus, ProjectStatusEnum.CREATED.getValue(), ProjectStatusEnum.ACTIVATED.getValue(), ProjectStatusEnum.FINISHED.getValue());
        //sampleProjectWrapper.eq(QdesProject::getPtype, ProjectPtypeEnum.SURVEY.getValue());
        //List<String> userAllSampleProjectIds = qdesProjectMapper.selectList(sampleProjectWrapper).stream().map(QdesProject::getId).collect(Collectors.toList());
        ////查询当前用户下的所有项目id
        //List<String> userAllProjectIds = qdesProjectentryMapper.selectList(new LambdaQueryWrapper<QdesProjectentry>().eq(QdesProjectentry::getUserId, userId))
        //        .stream().map(QdesProjectentry::getProjectId).collect(Collectors.toList());
        //todo 这里跳过一个添加sample project的逻辑。分页查询完之后又增加了sample，是否会导致pagesize增大？
        //todo 跳过一个塞值的动作，看不懂的操作。前面分页查了projectEntry,后面又拿id in 查一般，然后再一遍。

        //添加权限合并到补充数据中去了

        //返回
        QuestionLibProjectHomeVo questionLibProjectHomeVo = new QuestionLibProjectHomeVo();
        questionLibProjectHomeVo.setPage(page.getCurrent());
        questionLibProjectHomeVo.setTotalCount(total);
        questionLibProjectHomeVo.setRows(homePageVos);
        return questionLibProjectHomeVo;
    }

    /**
     * 统计题目数
     *
     * @param qstruct
     * @return
     */
    private int getQcount(QdesQstruct qstruct) {
        String data = qstruct.getData();
        if (StrUtil.isEmpty(data)) {
            return 0;
        }
        return getCount(JSONObject.parseObject(data), 0);
    }

    /**
     * 递归统计
     *
     * @param jsonObject
     * @param count
     * @return
     */
    private int getCount(JSONObject jsonObject, int count) {
        JSONArray items = jsonObject.getJSONArray("items");
        //加个异常数据处理
        if (ObjectUtils.isEmpty(items)) {
            return count;
        }
        for (int i = 0; i < items.size(); i++) {
            JSONObject item = items.getJSONObject(i);
            String type = item.getString("type");
            if ("G".equals(type)) {
                count += getCount(item, count);
            } else {
                count++;
            }
        }
        return count;
    }

    /**
     * 获取答题完成数量统计
     *
     * @param project
     * @return
     */
    private Integer getSurveyFinishedCount(QdesProject project) {
        String worktableInfo = project.getWorktableInfo();
        if (StrUtil.isEmpty(worktableInfo) || StrUtil.equals("{}", worktableInfo)) {
            return 0;
        }
        String key = "QDES:rspd_count_" + project.getId();
        //Integer count = (Integer) redisTemplate.opsForValue().get(key);
        Object countObj = redisService.get(key);
        if (countObj != null) {
            return (Integer) countObj;
        }
        //todo 这里通过bi的grpc去做统计
        return null;
    }

    /**
     * 3个值
     *
     * @param vo
     * @param project
     */
    private void fillNpsInfo(QuestionLibProjectHomePageVo vo, QdesProject project) {
        String satisfaction_nps_key = "qdes:satisfaction_nps_" + project.getId();
        Object satisfaction_nps_obj = redisService.get(satisfaction_nps_key);

        JSONArray satisfaction_nps = null;
        if (satisfaction_nps_obj != null) {
            satisfaction_nps = JSONArray.parseArray(satisfaction_nps_obj.toString());
        }
        //len(satisfaction_nps)==2说明还没有生成nps的推荐者等占比信息
        if ((!ObjectUtils.isEmpty(satisfaction_nps) && satisfaction_nps.size() == 2)
                //如果缓存中没有就去查询，并且查询之后设置缓存
                || ObjectUtils.isEmpty(satisfaction_nps)) {
            try {
                satisfaction_nps = getBiSatisfactionNpsByPid(project);
            } catch (Exception e) {
                satisfaction_nps = new JSONArray(Arrays.asList(null, null, Arrays.asList(null, null, null)));
                log.error("获取项目首页的nps和满意度值 error:", e);
            }
        }
        vo.setSatisfaction(satisfaction_nps.getBigDecimal(0) == null ? null : satisfaction_nps.getBigDecimal(0).doubleValue());
        vo.setNps(satisfaction_nps.get(1) == null ? null : satisfaction_nps.getDouble(1));
        vo.setNpsDetailList(satisfaction_nps.getJSONArray(2).toJavaList(Double.class));
        redisService.set(satisfaction_nps_key, satisfaction_nps.toJSONString(), 60 * 10, TimeUnit.SECONDS);
    }

    /**
     * 通过调用BI grpc 获取项目首页的nps和满意度值
     * 通过ck先查出原始加和值，再在程序中做简单除法计算
     * Promoter: 推荐者
     * Detractors: 贬损者
     * Passives: 被动者
     *
     * @param project 项目对象
     * @return
     */
    private JSONArray getBiSatisfactionNpsByPid(QdesProject project) {
        Double sat = null, nps = null;
        List<Double> npsDetailList;
        JSONArray res = new JSONArray();
        if (project.getStatus().equals(PROJECT_STATUS_CREATED.getNumber()) || (StrUtil.isEmpty(project.getWorktableInfo())) || CollUtil.isEmpty(JSONObject.parseObject(project.getWorktableInfo()))) {
            npsDetailList = Arrays.asList(null, null, null);
            res.add(sat);
            res.add(nps);
            res.add(npsDetailList);
            return res;
        }
        JSONObject worktable_info = JSONObject.parseObject(project.getWorktableInfo());
        String worktable_id = worktable_info.getString("worktable_id");
        String seq_col_id = worktable_info.getString("seq_col_id");
        String nps_col = worktable_info.getString("first_nps_col");
        String sat_col = worktable_info.getString("first_sat_col");
        JSONArray cal_score = worktable_info.getJSONArray("cal_score");

        JSONObject nps_rule_dict = new JSONObject();
        nps_rule_dict.put("nps_Promoter", Arrays.asList(OptionEnum.Option.GTE.name(), 9));
        nps_rule_dict.put("nps_Detractors", Arrays.asList(OptionEnum.Option.LTE.name(), 6));
        nps_rule_dict.put("nps_Passives", Arrays.asList(OptionEnum.Option.IN.name(), Arrays.asList(7, 8)));
        nps_rule_dict.put("nps_Total", new ArrayList<>());

        JSONObject sat_rule_dict = new JSONObject();
        sat_rule_dict.put("Sat", Arrays.asList(OptionEnum.Option.IN.name(), cal_score));
        sat_rule_dict.put("Total", new ArrayList<>());

        if (StrUtil.isEmpty(sat_col) || CollUtil.isEmpty(cal_score)) {
            //没有满意度题，获此题没有选项
            sat = null;
        } else {
            Map<String, Integer> sat_dict = getBiSatisfactionNpsByPid(worktable_id, sat_col, sat_rule_dict, seq_col_id);
            if (sat_dict.get("Total") == 0) {
                sat = null;
            } else {
                sat = new BigDecimal(sat_dict.get("Sat") * 100.0 / sat_dict.get("Total")).setScale(1, RoundingMode.HALF_UP).doubleValue();
            }
        }
        if (StrUtil.isEmpty(nps_col)) {
            nps = null;
            npsDetailList = Arrays.asList(null, null, null);
        } else {
            Map<String, Integer> npsDict = getBiSatisfactionNpsByPid(worktable_id, nps_col, nps_rule_dict, seq_col_id);
            if (npsDict.get("Total") == 0) {
                nps = null;
                npsDetailList = Arrays.asList(null, null, null);
            } else {
                nps = new BigDecimal(npsDict.get("Promoter")).divide(new BigDecimal(npsDict.get("Total")), 3, RoundingMode.HALF_UP).multiply(new BigDecimal(100))
                        .subtract(new BigDecimal(npsDict.get("Detractors")).divide(new BigDecimal(npsDict.get("Total")), 3, RoundingMode.HALF_UP).multiply(new BigDecimal(100))).doubleValue();
                npsDetailList = new ArrayList<>();
                npsDetailList.add(new BigDecimal(npsDict.get("Detractors")).divide(new BigDecimal(npsDict.get("Total")), 3, RoundingMode.HALF_UP).multiply(new BigDecimal(100)).doubleValue());
                npsDetailList.add(new BigDecimal(npsDict.get("Passives")).divide(new BigDecimal(npsDict.get("Total")), 3, RoundingMode.HALF_UP).multiply(new BigDecimal(100)).doubleValue());
                npsDetailList.add(new BigDecimal(npsDict.get("Promoter")).divide(new BigDecimal(npsDict.get("Total")), 3, RoundingMode.HALF_UP).multiply(new BigDecimal(100)).doubleValue());
            }
        }
        res.add(sat);
        res.add(nps);
        res.add(npsDetailList);
        return res;
    }

    private Map<String, Integer> getBiSatisfactionNpsByPid(String worktableId, String col, JSONObject rule_dict, String seq_col_id) {
        Map<String, Integer> resultDict = new HashMap<>();
        // 组装NewColumn
        NewColumn countColumn = NewColumn.newBuilder().setCol("total_count").setName("total_count").setColumnId(seq_col_id).setDataType(DataTypeEnum.DataType.NUMBER).build();
        // python中Func是0对应的count， java中是1对应的count，目前按照java的来
        Pivot pivot = Pivot.newBuilder().setName("total_count").addMainIndexes(" ").setAggable(Boolean.TRUE).setFunc(FuncEnum.Func.COUNT).setMultiFunc(MultiFuncEnum.MultiFunc.UNSPECIFIED).build();
        rule_dict.forEach((name, rule) -> {
            List<Filter> filter_list = new ArrayList<>();
            if (name.contains("nps")) {
                //计算卡片nps时，计算所有数据，不筛选成功完成的数据
                name = name.split("_")[1];
            } else {
                filter_list.add(Filter.newBuilder().setCol("status").setOption(OptionEnum.Option.EQ).setDataType(DataTypeEnum.DataType.STRING).addAllValues(Collections.singletonList("成功完成")).build());
            }
            if (CollUtil.isNotEmpty((List<?>) rule)) {
                List<Object> ruleList = (List<Object>) rule;
                filter_list.add(Filter.newBuilder().setCol(col).setOption(OptionEnum.Option.valueOf(ruleList.get(0).toString())).setDataType(DataTypeEnum.DataType.NUMBER).addAllValues(Convert.toList(String.class, ruleList.get(1))).build());
            } else {
                filter_list.add(Filter.newBuilder().setCol(col).setOption(OptionEnum.Option.NEP).setDataType(DataTypeEnum.DataType.NUMBER).addAllValues(Collections.singletonList("")).build());
            }
            int countResult = biGrpcClient.countData(worktableId, filter_list, countColumn, pivot);
            resultDict.put(name, countResult);
        });
        return resultDict;
    }

    /**
     * 判断是否拷贝已结束
     *
     * @param qdesProject
     * @param orgId
     * @return
     */
    private Boolean getCopyFinished(@Nonnull QdesProject qdesProject, String orgId) {
        Boolean isCopyDemo = qdesProject.getIsCopyDemo();
        if (isCopyDemo != null && isCopyDemo) {
            LocalDateTime createdDt = qdesProject.getCreatedDt();
            //创建时间大于1h,判断为完成
            if (Math.abs(LocalDateTimeUtil.between(createdDt, LocalDateTime.now(), ChronoUnit.SECONDS)) > 3600) {
                return true;
            } else {
                //看缓存有没有
                //Object o = redisTemplate.opsForValue().get("USER:COPY_SAMPLE:FLAG:" + orgId);
                Object o = redisService.get("USER:COPY_SAMPLE:FLAG:" + orgId);
                if (o == null) {
                    return false;
                }
                if (o instanceof String) {
                    return Boolean.parseBoolean((String) o);
                }
            }
        }
        return null;
    }

    private String getCreator(UserDto userV1) {
        if (userV1 != null) {
            return userV1.getName();
        }
        return "系统";
    }

    /**
     * DeliverTask = 60
     * python那边权限增加了60，当vo.user_id=userId的时候
     * 不相等不处理
     * 只有审核者才加
     *
     * @param vo
     * @param userId
     * @param isChecker
     * @return
     */
    private String addPermission(QuestionLibProjectHomePageVo vo, String userId, boolean isChecker) {
        String permissions = vo.getPermissionsArry();
        if (!isChecker) {
            return permissions;
        }
        if (StrUtil.isEmpty(permissions) || StrUtil.equals("[]", permissions)) {
            return permissions;
        }
        String projectEntryUserId = vo.getUserId();
        if (!StrUtil.equals(projectEntryUserId, userId)) {
            return permissions;
        }
        JSONArray objects = JSON.parseArray(permissions);
        Set<Object> set = new TreeSet<>(objects);
        set.add(PROJECT_PERMISSION_DELIVER_TASK);
        return JSON.toJSONString(set);
    }

    /**
     * 题目库-添加模板-问卷项目选中后详情查询
     *
     * @param id
     * @return
     */
    @Override
    public QuestionLibProjectDetailVo projectsDetail(String id) {

        //根据id查询项目信息
        String orgId = SecurityContextHolder.getOrgId();
        LambdaQueryWrapper<QdesProject> qw = new LambdaQueryWrapper<QdesProject>()
                .eq(QdesProject::getId, id)
                .eq(StrUtil.isNotEmpty(orgId), QdesProject::getCompanyId, orgId);
        QdesProject qdesProject = qdesProjectMapper.selectList(qw).stream().findFirst().orElse(new QdesProject());

        //转换
        //QuestionLibProjectDetailVo vo = new QuestionLibProjectDetailVo();
        //BeanUtil.copyProperties(qdesProject, vo, true);
        //return vo;
        return qdesProjectConvertMapper.project2QlibProjectDetailVo(qdesProject);
    }

    /**
     * 这里是根据项目编号查题目详情
     *
     * @param id      结构表id
     * @param withQ
     * @param timeMap
     * @return
     */
    @Override
    public QstructsDetailVo qstructsDetail(String id, String withQ, String timeMap) {

        //根据id查询问卷结构信息
        log.info("请求参数：id={},withQ={},timeMap={}", id, withQ, timeMap);
        QdesQstruct qdesQstructs = qdesQstructMapper.selectById(id);
        if (qdesQstructs == null) {
            throw new BusinessException("未查到问卷结构信息");
        }

        //如果是要带题目信息，则补入题目信息
        if (QdesConstant.YES.equals(withQ)) {
            qdesQstructs.setData(fillDataQuestions(qdesQstructs.getId(), qdesQstructs.getData()));
        }

        //转换
        //QuestionLibQstructsDetailVo vo = new QuestionLibQstructsDetailVo();
        //BeanUtil.copyProperties(qdesQstructs, vo, true);
        //return vo;
        return qstructConvertMapper.qstruct2Vo(qdesQstructs);
    }

    /**
     * 结构data填充题目数据
     *
     * @param qstructsData
     * @param structId
     * @return
     */
    @Override
    public String fillDataQuestions(String structId, String qstructsData) {
        //查询题目数据
        LambdaQueryWrapper<QdesQuestion> eq = new LambdaQueryWrapper<>();
        eq.eq(QdesQuestion::getStructId, structId);
        List<QdesQuestion> qList = qdesQuestionMapper.selectList(eq);
        if (CollUtil.isEmpty(qList)) {
            return qstructsData;//原样返回
        }

        //
        Map<Integer, JSONObject> qMap = qList.stream().collect(Collectors.toMap(QdesQuestion::getGid, q -> {
            JSONObject ojb = JSON.parseObject(q.getData());
            ojb.put("id", q.getId());
            return ojb;
        }));
        //
        JSONObject qstructsDataJson = JSON.parseObject(qstructsData);
        questionService.fillData(qMap, qstructsDataJson);
        return qstructsDataJson.toJSONString();
    }

}